package lumis.portal.group.serialization;

import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.group.GroupConfig;
import lumis.portal.group.IGroupManager;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.page.PageConfig;
import lumis.portal.principal.PrincipalConfig;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.ISerializationMessageConfigNodeBuilder;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationMessageConfigNode;
import lumis.portal.serialization.DeserializationContext.ImportMemberRules.ForCreate;
import lumis.portal.serialization.DeserializationContext.ImportMemberRules.ForUpdate;
import lumis.portal.serialization.SerializationMessageConfigNode.RecursionStopPoints;
import lumis.portal.serialization.operation.ConfigDependencyAnalyzer;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.portal.serialization.operation.IConfigPersister;

import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Manager for Group serialization
 *
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 4.0.7
 */
public class GroupSerializationManager implements IGroupSerializationManager
{
	private final String[] primaryKeyFieldNames = new String[] {"id"};
	protected final List<ConfigForeignKey> foreignKeys = Arrays.asList(new ConfigForeignKey[] {
			new ConfigForeignKey("channelId"),
			new ConfigForeignKey("accessControlListId")
		});
	protected final List<ConfigForeignKey> memberForeignKeys = Arrays.asList(new ConfigForeignKey[] {
			new ConfigForeignKey("memberId", null, false, false, true, ManagerFactory.getPrincipalManager())
		});

	private final String configAlias = "group";

	// DESERIALIZATION

	public void deserialize(DeserializationContext deserializationContext, GroupSerializableConfig groupSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing group '" + groupSerializableConfig.getId() + "'");

		// deserialize access control list
		ManagerFactoryInternal.getGroupAclSerializationManager().deserialize(deserializationContext, groupSerializableConfig.getAccessControlList());

		// deserialize config
		GroupConfig groupConfig = ManagerFactoryInternal.getSerializationManager2().deserializeObject(GroupConfig.class, deserializationContext.getDirectory(), getConfigPath(deserializationContext, groupSerializableConfig.getId()), configAlias, false);

		// apply id mappings
		deserializationContext.applyPrimaryKeyFieldIdMappings(groupConfig, primaryKeyFieldNames);
		deserializationContext.applyForeignKeyFieldIdMappings(groupConfig, foreignKeys);

		String channelId = groupConfig.getChannelId();
		if (deserializationContext.isCreateNewIds() && channelId != null)
		{
			ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(
					deserializationContext.getSessionConfig(), channelId, deserializationContext.getTransaction());
			if (channelConfig.hasLocalGroups())
			{
				// here the local group prefix should already be mapped. so, we
				// must get the original prefix to replace in group alias for
				// the new one..
				String newGroupPrefix = channelConfig.getLocalGroupPrefix();
				String originalPrefix = deserializationContext.getOriginalGroupPrefix(newGroupPrefix);
				Pattern pattern = Pattern.compile("^" + originalPrefix + "\\.");
				String alias = groupConfig.getAlias();
				Matcher m = pattern.matcher(alias);
				alias = m.replaceFirst(newGroupPrefix + ".");
				groupConfig.setAlias(alias);
			}
			else
			{
				// it shoudn't happen
				// TODO define what to do
			}
		}

		// add or update config
		IGroupManager groupManager = ManagerFactory.getGroupManager();
		boolean updatingExistingGroup = false;
		if (deserializationContext.isUpdateIfElementExists())
		{
			// check if group exists
			try
			{
				if(groupManager.get(deserializationContext.getSessionConfig(), groupConfig.getId(), deserializationContext.getTransaction()) != null);
					updatingExistingGroup = true;
			}
			catch (PortalObjectNotFoundException e)
			{
			}
		}

		if(updatingExistingGroup)
		{
			deserializationContext.logDebug("updating group:", groupConfig);
			groupManager.update(deserializationContext.getSessionConfig(), groupConfig, deserializationContext.getTransaction());

			ForUpdate forUpdate = deserializationContext.getImportMemberRules().getForUpdate();

			// delete all old members;
			for (String memberId : groupManager.getMembers(deserializationContext.getSessionConfig(), groupConfig.getId(), IGroupManager.FILTER_TYPE_USERS_AND_GROUPS, false, deserializationContext.getTransaction()))
			{
				boolean deleteMember = false;
				if(forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS))
				{
					deleteMember = true;
				}
				else
				{
					PrincipalConfig principalConfig = ManagerFactory.getPrincipalManager().get(deserializationContext.getSessionConfig(), memberId, deserializationContext.getTransaction());

					if(principalConfig.getType() == PrincipalConfig.TYPE_GROUP)
					{
						if(forUpdate.equals(ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP) || forUpdate.equals(ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS))
						{
							deleteMember = true;
						}
						else if(principalConfig.getChannelId() != null)
						{
							// delete only local members
							// make sure the channel id of the member is within the root channel structure.
							 String rootChannelMappedId = deserializationContext.getIdMapping(deserializationContext.getRootChannelId());
							String principalChannelMappedId = deserializationContext.getIdMapping(principalConfig.getChannelId());
							if(rootChannelMappedId.equals(principalChannelMappedId) ||
									 ManagerFactory.getChannelManager().getPathIds(deserializationContext.getSessionConfig(), principalChannelMappedId, deserializationContext.getTransaction()).contains(rootChannelMappedId))
								 	deleteMember = true;
						}
					}
				}

				if(deleteMember)
				{
					deserializationContext.logDebug(" Deleting member '" + memberId + "'");
					groupManager.deleteMember(deserializationContext.getSessionConfig(), groupConfig.getId(), memberId, deserializationContext.getTransaction());
				}
			}
		}
		else
		{
			// adding group
			deserializationContext.logDebug("adding group:", groupConfig);
			groupManager.add(deserializationContext.getSessionConfig(), groupConfig, deserializationContext.getTransaction());
		}

		// persist dependent configs
		deserializationContext.persistDependentConfigs(groupSerializableConfig.getId());

		// deserialize members
		for (GroupMemberSerializableConfig groupMemberSerializableConfig : groupSerializableConfig.getMembers())
			deserializeMember(deserializationContext, groupMemberSerializableConfig, groupConfig.getId(), updatingExistingGroup);
	}

	private void deserializeMember(DeserializationContext deserializationContext, GroupMemberSerializableConfig groupMemberSerializableConfig, String groupId, boolean updatingExistingGroup) throws PortalException
	{
		boolean addMember = false;
		ForCreate forCreate = deserializationContext.getImportMemberRules().getForCreate();
		ForUpdate forUpdate = deserializationContext.getImportMemberRules().getForUpdate();

		if( (updatingExistingGroup && forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS)) ||
			(!updatingExistingGroup && forCreate.equals(DeserializationContext.ImportMemberRules.ForCreate.ALL_MEMBERS)))
		{
			addMember = true;
		}
		else
		{
			if(groupMemberSerializableConfig.getPrincipalType() == PrincipalConfig.TYPE_GROUP)
			{
				if( (updatingExistingGroup && forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP)) ||
					(!updatingExistingGroup && forCreate.equals(DeserializationContext.ImportMemberRules.ForCreate.ALL_MEMBERS_OF_TYPE_GROUP)))
				{
					addMember = true;
				}
				else if( (updatingExistingGroup && forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS)) ||
						(!updatingExistingGroup && forCreate.equals(DeserializationContext.ImportMemberRules.ForCreate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS)))
				{
					if(groupMemberSerializableConfig.isExternal())
					{
						// check if member exists
						try
						{
							addMember = true;
							ManagerFactory.getGroupManager().get(deserializationContext.getSessionConfig(), groupMemberSerializableConfig.getId(), deserializationContext.getTransaction());
						}
						catch(PortalObjectNotFoundException e)
						{
							addMember = false;
						}
					}
					else
					{
						addMember =true;
					}
				}
				else if(groupMemberSerializableConfig.getChannelId() != null)
				{
					// adds only local members
					// make sure the channel id of the member is within the root channel structure.
					 String rootChannelMappedId = deserializationContext.getIdMapping(deserializationContext.getRootChannelId());
					String principalChannelMappedId = deserializationContext.getIdMapping(groupMemberSerializableConfig.getChannelId());
					if(rootChannelMappedId.equals(principalChannelMappedId) ||
							 ManagerFactory.getChannelManager().getPathIds(deserializationContext.getSessionConfig(), principalChannelMappedId, deserializationContext.getTransaction()).contains(rootChannelMappedId))
						addMember = true;
				}
			}
		}

		if(addMember)
		{
			deserializationContext.logDebug("Deserializing group member '" + groupMemberSerializableConfig.getId() + "'");
			GroupMemberConfig groupMemberConfig = new GroupMemberConfig(groupId, groupMemberSerializableConfig.getId());

			// analyze dependencies and persist configuration
			IConfigPersister groupMemberConfigPersister = getMemberConfigPersister();
			ConfigDependencyAnalyzer<?> dependencyAnalyzer = new ConfigDependencyAnalyzer<GroupMemberConfig>(deserializationContext, memberForeignKeys, groupMemberConfig);
			dependencyAnalyzer.persist(groupMemberConfigPersister, false);
		}
	}

	private IConfigPersister getMemberConfigPersister()
	{
		return new IConfigPersister()
		{
			public boolean persistConfig(DeserializationContext deserializationContext, Object config, boolean firstPersistence) throws PortalException
			{
				boolean persisted = false;

				GroupMemberConfig groupMemberConfig = (GroupMemberConfig)config;

				// apply foreign key id mappings and analyze dependencies
				ConfigDependencyAnalyzer<GroupMemberConfig> dependencyAnalyzer = new ConfigDependencyAnalyzer<GroupMemberConfig>(deserializationContext, memberForeignKeys, groupMemberConfig);
				if (dependencyAnalyzer.foreignKeysExists())
				{
					GroupMemberConfig mappedGroupMemberConfig = dependencyAnalyzer.getMappedPersistableConfig();
					deserializationContext.logDebug("Persisting group member:", mappedGroupMemberConfig);
					ManagerFactory.getGroupManager().addMember(deserializationContext.getSessionConfig(), mappedGroupMemberConfig.getGroupId(), mappedGroupMemberConfig.getMemberId(), deserializationContext.getTransaction());
					persisted = true;
				}

				return persisted;
			}
		};
	}

	// SERIALIZATION

	public GroupSerializableConfig serialize(SerializationContext serializationContext, GroupConfig groupConfig) throws PortalException
	{
		// serialize config
		ManagerFactoryInternal.getSerializationManager2().serializeObject(groupConfig, serializationContext.getDirectory(), getConfigPath(serializationContext, groupConfig.getId()), configAlias);

		// build structure object
		GroupSerializableConfig groupSerializableConfig = getSerializableConfig(groupConfig);

		// serialize access control list
		groupSerializableConfig.setAccessControlList(ManagerFactoryInternal.getGroupAclSerializationManager().serialize(serializationContext, groupConfig.getId()));

		// get members to be serialized. includeUserMembership option may affect which ones.
		Collection<String> members;
		if(serializationContext.getIncludeUserMembership())
			members = ManagerFactory.getGroupManager().getMembers(serializationContext.getSessionConfig(), groupConfig.getId(), IGroupManager.FILTER_TYPE_USERS_AND_GROUPS, false, serializationContext.getTransaction());
		else
			members = ManagerFactory.getGroupManager().getMembers(serializationContext.getSessionConfig(), groupConfig.getId(), IGroupManager.FILTER_TYPE_ONLY_GROUPS, false, serializationContext.getTransaction());

		for (String groupMemberId : members)
		{
			GroupMemberSerializableConfig groupMemberSerializableConfig = serializeMember(serializationContext, groupConfig, groupMemberId);
			groupSerializableConfig.getMembers().add(groupMemberSerializableConfig);
		}

		return groupSerializableConfig;
	}

	private GroupSerializableConfig getSerializableConfig(GroupConfig groupConfig)
	{
		GroupSerializableConfig groupSerializableConfig = new GroupSerializableConfig();
		groupSerializableConfig.setId(groupConfig.getId());
		groupSerializableConfig.setName(groupConfig.getName());
		return groupSerializableConfig;
	}

	private GroupMemberSerializableConfig serializeMember(final SerializationContext serializationContext, final GroupConfig groupConfig, String groupMemberId) throws PortalException
	{
		// build structure object
		GroupMemberSerializableConfig groupMemberSerializableConfig = getMemberSerializableConfig(serializationContext, groupMemberId);

		// analyze external dependencies
		ISerializationMessageConfigNodeBuilder serializationMessageConfigNodeBuilder = new ISerializationMessageConfigNodeBuilder()
		{
			public SerializationMessageConfigNode build() throws PortalException
			{
				return getSerializationMessageConfigNode(serializationContext, groupConfig);
			}
		};
		ManagerFactoryInternal.getPrincipalSerializationManager().analyzeExternalDependencies(serializationContext, serializationMessageConfigNodeBuilder, groupMemberId);

		// return structure object
		return groupMemberSerializableConfig;
	}

	private GroupMemberSerializableConfig getMemberSerializableConfig(SerializationContext serializationContext, String groupMemberId) throws PortalException
	{
		GroupMemberSerializableConfig groupMemberSerializableConfig = new GroupMemberSerializableConfig();
		groupMemberSerializableConfig.setId(groupMemberId);

		PrincipalConfig principalConfig = ManagerFactory.getPrincipalManager().get(serializationContext.getSessionConfig(), groupMemberId, serializationContext.getTransaction());
		groupMemberSerializableConfig.setPrincipalType(principalConfig.getType());
		groupMemberSerializableConfig.setChannelId(principalConfig.getChannelId());

		groupMemberSerializableConfig.setExternal(!serializationContext.getChannelIds().contains(principalConfig.getChannelId()) && !serializationContext.getGlobalGroups().contains(groupMemberId));

		return groupMemberSerializableConfig;
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, GroupConfig groupConfig) throws PortalException
	{
		return getSerializationMessageConfigNode(baseSerializationContext, groupConfig, EnumSet.of(RecursionStopPoints.None));
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, GroupConfig groupConfig, EnumSet<RecursionStopPoints> recursionStopPoints) throws PortalException
	{
		GroupSerializationMessageConfig groupSerializationMessageConfig = new GroupSerializationMessageConfig();
		groupSerializationMessageConfig.setId(groupConfig.getId());
		groupSerializationMessageConfig.setName(groupConfig.getName());
		groupSerializationMessageConfig.setGlobal(groupConfig.getChannelId() == null);

		SerializationMessageConfigNode serializationMessageConfigNode = new SerializationMessageConfigNode();
		serializationMessageConfigNode.setSerializationMessageConfig(groupSerializationMessageConfig);
		if (groupConfig.getChannelId() != null) // local group
		{
			if (!recursionStopPoints.contains(RecursionStopPoints.Channel))
			{
				ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(baseSerializationContext.getSessionConfig(), groupConfig.getChannelId(), baseSerializationContext.getTransaction());
				serializationMessageConfigNode.getParentNodes().add(ManagerFactoryInternal.getChannelSerializationManager().getSerializationMessageConfigNode(baseSerializationContext, channelConfig));
			}
			serializationMessageConfigNode.setNavigateScript("window.parent.opener.document.location.href='../../../../"+PageConfig.PAGE_MAIN+"?"+PortalRequestParameters.PAGE_PARAMETER_CHANNELID+"="+groupConfig.getChannelId()+"&"+PortalRequestParameters.PAGE_PARAMETER_PAGEID+"="+PageConfig.PAGE_ID_ADMIN_LOCALGROUPS+"';return false;");
		}
		else
		{
			serializationMessageConfigNode.setNavigateScript("window.parent.opener.document.location.href='../../../../hyperlink.jsp?" + PortalRequestParameters.PAGE_PARAMETER_INTERFACE + "=lumis.service.portalmanagement.group.administration';return false;");
		}
		return serializationMessageConfigNode;
	}

	// GENERAL

	public void setupSerializationContext(BaseSerializationContext serializationContext)
	{
		// define serialization aliases
		serializationContext.getSerializationSettings().getClassAliases().put(GroupSerializableConfig.class, "group");
		serializationContext.getSerializationSettings().getClassAliases().put(GroupMemberSerializableConfig.class, "groupMember");
	}

	/**
	 * Get the full path for the serialization file.
	 * @param deserializationContext
	 * @param configId
	 * @return
	 */
	private String getConfigPath(BaseSerializationContext deserializationContext, String configId)
	{
		return "groups/" + configId + ".xml";
	}
}
